/**
 * Copyright (c) 2011 INRIA.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 **/
package org.objectweb.proactive.extensions.p2p.structured.overlay;

import java.io.Serializable;
import java.util.UUID;

import org.objectweb.proactive.Body;
import org.objectweb.proactive.EndActive;
import org.objectweb.proactive.InitActive;
import org.objectweb.proactive.api.PAActiveObject;
import org.objectweb.proactive.extensions.p2p.structured.exceptions.DispatchException;
import org.objectweb.proactive.extensions.p2p.structured.exceptions.NetworkAlreadyJoinedException;
import org.objectweb.proactive.extensions.p2p.structured.exceptions.NetworkNotJoinedException;
import org.objectweb.proactive.extensions.p2p.structured.factories.PeerFactory;
import org.objectweb.proactive.extensions.p2p.structured.messages.RequestResponseMessage;
import org.objectweb.proactive.extensions.p2p.structured.messages.request.Request;
import org.objectweb.proactive.extensions.p2p.structured.messages.response.Response;
import org.objectweb.proactive.extensions.p2p.structured.operations.AsynchronousOperation;
import org.objectweb.proactive.extensions.p2p.structured.operations.ResponseOperation;
import org.objectweb.proactive.extensions.p2p.structured.operations.SynchronousOperation;
import org.objectweb.proactive.extensions.p2p.structured.providers.SerializableProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * PeerImpl is a concrete implementation of {@link Peer}. It is composed of a
 * {@link StructuredOverlay} which allows to have several implementations of
 * common operations for each peer-to-peer protocol to implement. This class
 * acts as a Facade in order to simplify interface (easier to use, understand
 * and test).
 * <p>
 * Warning, this class must not be instantiate directly. In order to create a
 * new active peer you have to use the {@link PeerFactory}.
 * 
 * @author lpellegr
 */
public class PeerImpl implements Peer, InitActive, EndActive, Serializable {

    private static final long serialVersionUID = 1L;

    protected static Logger logger = LoggerFactory.getLogger(PeerImpl.class);

    protected transient StructuredOverlay overlay;

    /**
     * The no-argument constructor as commanded by ProActive.
     */
    public PeerImpl() {
    }

    /**
     * Constructs a new peer using the specified overlay. Warning, you must use
     * {@link PeerFactory} to instantiate a new active peer.
     * 
     * @param overlayProvider
     *            the provider to use for creating the {@link StructuredOverlay}
     *            embedded by the peer.
     */
    public PeerImpl(
            SerializableProvider<? extends StructuredOverlay> overlayProvider) {
        this.overlay = overlayProvider.get();
    }

    /**
     * {@inheritDoc}
     */
    public boolean init(Peer stub,
                        SerializableProvider<? extends StructuredOverlay> overlay) {
        if (this.overlay == null) {
            this.overlay = overlay.get();
            this.overlay.stub = stub;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void initActivity(Body body) {
        body.setImmediateService("receiveImmediateService", false);

        // these methods do not change the state of the peer
        body.setImmediateService("equals", false);
        body.setImmediateService("getId", false);
        body.setImmediateService("hashCode", false);
        body.setImmediateService("toString", false);
        body.setImmediateService("getType", false);

        body.setImmediateService("send", false);
        body.setImmediateService("route", false);

        // tests if overlay is null for component instantiation use-case
        if (this.overlay != null) {
            this.overlay.stub = (Peer) PAActiveObject.getStubOnThis();
        }

        // receive cannot be handled as immediate service
        PAActiveObject.removeImmediateService("receive");
    }

    /**
     * {@inheritDoc}
     */
    public void endActivity(Body body) {
        // TODO: enable the leave operation when it works!

        // if (this.overlay.activated.get()) {
        // try {
        // this.leave();
        // } catch (NetworkNotJoinedException e) {
        // e.printStackTrace();
        // }
        // }

        if (this.overlay.datastore != null
                && this.overlay.datastore.isInitialized()) {
            this.overlay.datastore.close();
        }
    }

    /**
     * {@inheritDoc}
     */
    public UUID getId() {
        return this.overlay.id;
    }

    /**
     * {@inheritDoc}
     */
    public OverlayType getType() {
        return this.overlay.getType();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isActivated() {
        return this.overlay.activated.get();
    }

    /**
     * {@inheritDoc}
     */
    public boolean create() throws NetworkAlreadyJoinedException {
        if (this.overlay.activated.compareAndSet(false, true)) {
            return this.overlay.create();
        } else {
            throw new NetworkAlreadyJoinedException();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean join(Peer landmarkPeer) throws NetworkAlreadyJoinedException {
        if (this.overlay.activated.compareAndSet(false, true)) {
            if (!this.overlay.join(landmarkPeer)) {
                // a concurrent join operation has been detected
                // hence we have to reset the activated variable
                // to false in order to have the possibility to
                // try again later
                this.overlay.activated.set(false);
                return false;
            } else {
                return true;
            }
        } else {
            throw new NetworkAlreadyJoinedException();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean leave() throws NetworkNotJoinedException {
        if (this.overlay.activated.compareAndSet(true, false)) {
            return this.overlay.leave();
        } else {
            throw new NetworkNotJoinedException();
        }
    }

    /**
     * {@inheritDoc}
     */
    public ResponseOperation receive(SynchronousOperation operation) {
        return operation.handle(this.overlay);
    }

    /**
     * {@inheritDoc}
     */
    public void receive(AsynchronousOperation operation) {
        operation.handle(this.overlay);
    }

    /**
     * {@inheritDoc}
     */
    public ResponseOperation receiveImmediateService(SynchronousOperation operation) {
        return operation.handle(this.overlay);
    }

    /**
     * {@inheritDoc}
     */
    public void receiveImmediateService(AsynchronousOperation operation) {
        operation.handle(this.overlay);
    }

    /**
     * {@inheritDoc}
     */
    public void route(RequestResponseMessage<?> msg) {
        msg.route(this.overlay);
    }

    /**
     * {@inheritDoc}
     */
    public Response<?> send(Request<?> request) throws DispatchException {
        return this.overlay.messagingManager.dispatch(request);
    }

    /**
     * {@inheritDoc}
     */
    public String dump() {
        return this.overlay.dump();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        return obj instanceof PeerImpl
                && this.getId().equals(((PeerImpl) obj).getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.getId().hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        if (this.overlay == null) {
            return Integer.toString(System.identityHashCode(this));
        }

        return this.overlay.toString();
    }

}
